from django.db import models
from django.db.models.signals import post_save
from django.core.files import File
from django.urls import reverse
from django.utils import timezone

import os

from easy_thumbnails.fields import ThumbnailerImageField

# pip3 install django-solo-grappelli
from solo.models import SingletonModel

# apt install python3-django-taggit
from taggit.managers import TaggableManager

# pip3 install django-autoslug
from autoslug import AutoSlugField

import subprocess
import json

import logging
logger = logging.getLogger('django')

# Create your models here.

_MEDIA_STATE__NEW           = 0
_MEDIA_STATE__UNSUPPORTED   = 1
_MEDIA_STATE__IDENTIFY      = 2
_MEDIA_STATE__HASHING       = 4
_MEDIA_STATE__THUMBNAILS    = 8
_MEDIA_STATE__SAVED         = 16
_MEDIA_STATE__SPLITSTREAMS  = 32

def hash_for_file(hasher, f, block_size=100*1024):
    h = hasher()
    ''' Record the current position in the file object '''
    pos = f.tell()
    ''' Seek to 0 to calculate the hash properly (the whole file) '''
    f.seek(0)
    while True:
        data = f.read(block_size)
        logger.debug('reading %s bytes, found %s bytes' % (block_size, len(data)))
        if not data:
           break
        h.update(data)
    ''' Return to the original position '''
    f.seek(pos)
    logger.info('hash calculated to %s' % h.hexdigest())
    return h

class Media(models.Model):
    def media_upload_path(instance, filename):
        import base64
        parts = [ 'uploads', 'user', str(instance.user.id), base64.urlsafe_b64encode(instance.uri.encode()).decode(), 'original' ]
        return os.path.join(*parts)

    user  = models.ForeignKey('auth.User')
    uri     = models.TextField(db_index=True, editable=False, unique=True)
    upload  = models.FileField(upload_to=media_upload_path, editable=False)
    sha512  = models.CharField(max_length=128, db_index=True, editable=False)

    state   = models.IntegerField(default=_MEDIA_STATE__IDENTIFY) # _MEDIA_STATE__*

    title   = models.CharField(max_length=100)
    slug    = AutoSlugField(populate_from='title', unique_with='user')
    tags    = TaggableManager(blank=True)
    description = models.TextField(blank=True)

    poster  = models.OneToOneField('Thumbnail', related_name='poster', blank=True, null=True)

    uploaded    = models.DateTimeField(default=timezone.now, editable=False)
    pub_date    = models.DateTimeField(blank=True, null=True)

    _metadata = None

    def __str__(self):
        return self.title

    @property
    def permalink(self):
        return reverse('media_detail', kwargs={'pk':self.pk})

    def get_absolute_url(self):
        return self.permalink

    def get_original_url(self):
        return os.path.join('/media', self.upload.name)

    def set_state(self, state, save=True):
        old_state = self.state
        self.state = self.state | state
        logger.info('State changed for media {} with {}: {} => {}'.format(self.pk, state, old_state, self.state))
        if save is True:
            self.save() # saves the full object, maybe update_fields as optional?

    def check_state(self, state):
        logger.info('checking state {} for {}: {}'.format(self.state, state, self.state & state))
        if not self.state & state:
            raise ValueError('unexpected state {} for media {}, should be {}'.format(self.state, self.pk, state))

        return True

    def get_data_dir(self):
        return os.path.dirname(self.upload.name)

    def get_metadata_filename(self):
        return os.path.join(self.get_data_dir(), 'metadata.json')

    def get_metadata(self):
        if self._metadata:
            logger.info('metadata cached already')
            return self._metadata

        if self.state & _MEDIA_STATE__UNSUPPORTED:
            logger.info('unsupported media, cannot get metadata')
            raise ValueError('Media is unsupported, cannot get metadata.')

        ''' we need to have identified before metadata is available '''
        if not self.state & _MEDIA_STATE__IDENTIFY:
            logger.info('no metadata stored yet')
            raise ValueError('Metadata retrieval not available yet')

        if not os.path.exists(self.get_metadata_filename()):
            logger.info('Missing metadata file for media id=={}, generating new.'.format(self.id))
            save_media_metadata(self)

        logger.info('Opening metadata file {}'.format(self.get_metadata_filename()))
        with open(self.get_metadata_filename(), 'r') as jsonfile:
            self._metadata = json.load(jsonfile)

        return self.get_metadata()

    @property
    def stream_dir(self):
        return os.path.join(self.get_data_dir(), 'streams')

    @property
    def thumb_dir(self):
        return os.path.join(self.get_data_dir(), 'thumbnails')

class MediaFormats(models.Model):
    class Meta:
        unique_together = (('media', 'profile'),)

    media = models.ForeignKey(Media, models.CASCADE, editable=False)
    profile = models.CharField(max_length=64, editable=False)
    converted = models.FileField(editable=False)

class MediaProperty(models.Model):
    class Meta:
        unique_together = (('media', 'namespace', 'key'),)
    media = models.ForeignKey(Media, models.CASCADE)
    namespace = models.CharField(max_length=128)
    key = models.CharField(max_length=128)
    value = models.TextField()

class MediaStream(models.Model):
    class Meta:
        unique_together = (('media', 'stream_id'),)

    media = models.ForeignKey(Media, models.CASCADE, editable=False)
    stream_id = models.IntegerField(editable=False)
    codec_type = models.CharField(max_length=64, editable=False)
    stream_file = models.FileField(editable=False)

    @property
    def url(self):
        return os.path.join('/media', self.stream_file.name)
        

class SiteConfiguration(SingletonModel):
    site_name = models.CharField(max_length=128, default='Site Name')
    maintenance_mode = models.BooleanField(default=False)
    converting = models.BooleanField(default=False)

    def __unicode__(self):
        return u"Site Configuration"

    class Meta:
        verbose_name = "Site Configuration"

class MediaTask(models.Model):
    media   = models.ForeignKey(Media, models.CASCADE)
    task    = models.CharField(max_length=128)
    args    = models.TextField(default=None, blank=True, null=True)
    claimed = models.DateTimeField(default=None, blank=True, null=True)

    def start():
        import mediaconvert.tasks as tasks
        try:
            logger.info('Looking up task media_{}'.format(self.task))
            task = getattr(tasks, 'media_{}'.format(self.task))
        except AttributeError as e:
            logger.error('No media task is defined for {}.'.format(repr(self.task)))
            raise NotImplementedError('No media task is defined for {}.'.format(repr(self.task)))

        kwargs = json.loads(self.args) if self.args is not None else {}
        logger.info('Running task media_{} with args {}'.format(self.task, kwargs))
        ''' Maybe make sure to check it's a delayed task? '''
        task(self.media.pk, **kwargs)

class Thumbnail(models.Model):
    media   = models.ForeignKey(Media, models.CASCADE)
    image   = ThumbnailerImageField(height_field='height', width_field='width')
    height  = models.PositiveSmallIntegerField()
    width   = models.PositiveSmallIntegerField()

    def __str__(self):
        return os.path.basename(self.image.name)

    @property
    def url(self):
        return os.path.join('/media', self.image.name)

from autotask.tasks import delayed_task, periodic_task

@periodic_task(seconds=60)
def periodic_identify_media():
    m = Media.objects.filter(state=_MEDIA_STATE__NEW)[0]
    if m:
        from mediaconvert.tasks import media_identify
        media_identify(m.pk)

def run_next_task():
    task = MediaTask.objects.filter(claimed=None)[0]
    task.start()

@periodic_task(seconds=60)
def periodic_convert_media():
    import configparser
    config = configparser.ConfigParser()
    config.read('fmt_profiles.ini')

    ''' get next in queue and process '''
    pass

def enqueue_identify_media(sender, instance, created, **kwargs):
    if not created:
        return

    from mediaconvert.tasks import enqueue_media_task
    enqueue_media_task(instance, 'identify')

post_save.connect(enqueue_identify_media, sender=Media)

from chunked_upload.models import ChunkedUpload

class MediaChunkedUpload(ChunkedUpload):
    # Can't think of anything special we have to do here right now
    pass
